<html>
<head>
  <title>Evernote Export</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602498 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="624"/>

<div>
<span><div><div><span style="font-size: 16pt;">Redis</span></div><ol><li><div>缓存，毫无疑问这是Redis当今最为人熟知的使用场景。在提升服务器性能方面非常有效；一 些频繁被访问的数据，经常被访问的数据如果放在关系型数据库，每次查询的开销都会很 大，而放在redis中，因为redis 是放在内存中的可以很高效的访问</div></li><li><div>排行榜，在使用传统的关系型数据库（mysql oracle 等）来做这个事儿，非常的麻烦，而利 用Redis的SortSet(有序集合)数据结构能够简单的搞定；</div></li><li><div>计算器/限速器，利用Redis中原子性的自增操作，我们可以统计类似用户点赞数、用户访问 数等，这类操作如果用MySQL，频繁的读写会带来相当大的压力；限速器比较典型的使用场 景是限制某个用户访问某个API的频率，常用的有抢购时，防止用户疯狂点击带来不必要的压 力；</div></li><li><div>好友关系，利用集合的一些命令，比如求交集、并集、差集等。可以方便搞定一些共同好 友、共同爱好之类的功能；</div></li><li><div>简单消息队列，除了Redis自身的发布/订阅模式，我们也可以利用List来实现一个队列机制， 比如：到货通知、邮件发送之类的需求，不需要高可靠，但是会带来非常大的DB压力，完全 可以用List来完成异步解耦；</div></li><li><div>Session共享，以jsp为例，默认Session是保存在服务器的文件中，如果是集群服务，同一个 用户过来可能落在不同机器上，这就会导致用户频繁登陆；采用Redis保存Session后，无论 用户落在那台机器上都能够获取到对应的Session信息。</div></li></ol><div><br/></div><div><br/></div><div><br/></div><div>分布式数据库CAP原理</div><div>CAP简介</div><ol><li><div>传统的关系型数据库事务具备ACID：</div></li><ol><li><div>A：原子性</div></li><li><div>C：一致性</div></li><li><div>I：独立性</div></li><li><div>D：持久性</div></li></ol><li><div>分布式数据库的CAP:</div></li><ol><li><div>C（Consistency）：强一致性</div></li><ol><li><div>“all nodes see the same data at the same time”,即更新操作成功并返回客户端后，所 有节点在同一时间的数据完全一致，这就是分布式的一致性。一致性的问题在并发系统 中不可避免，对于客户端来说，一致性指的是并发访问时更新过的数据如何获取的问 题。从服务端来看，则是更新如何复制分布到整个系统，以保证数据最终一致。</div></li></ol><li><div>A（Availability）：高可用性</div></li><ol><li><div>可用性指“Reads and writes always succeed”，即服务一直可用，而且要是正常的响应 时间。好的可用性主要是指系统能够很好的为用户服务，不出现用户操作失败或者访问 超时等用户体验不好的情况。</div></li></ol><li><div>P（Partition tolerance）：分区容错性</div></li><ol><li><div>即分布式系统在遇到某节点或网络分区故障时，仍然能够对外提供满足一致性或可用性 的服务</div></li><li><div>分区容错性要求能够使应用虽然是一个分布式系统，而看上去却好像是在一个可以运转 正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了，其他剩下的机器 还能够正常运转满足系统需求，对于用户而言并没有什么体验上的影响。</div></li></ol></ol></ol><div><br/></div><div><br/></div><div>CAP理论</div><ol><li><div>CAP理论提出就是针对分布式数据库环境的，所以，P这个属性必须容忍它的存在，而且是必须具 备的。</div></li><li><div>因为P是必须的，那么我们需要选择的就是A和C。</div></li><li><div>大家知道，在分布式环境下，为了保证系统可用性，通常都采取了复制的方式，避免一个节点损 坏，导致系统不可用。那么就出现了每个节点上的数据出现了很多个副本的情况，而数据从一个节 点复制到另外的节点时需要时间和要求网络畅通的，所以，当P发生时，也就是无法向某个节点复 制数据时，这时候你有两个选择：</div></li><ol><li><div>选择可用性 A，此时，那个失去联系的节点依然可以向系统提供服务，不过它的数据就不能 保证是同步的了（失去了C属性）。</div></li><li><div>选择一致性C，为了保证数据库的一致性，我们必须等待失去联系的节点恢复过来，在这个过 程中，那个节点是不允许对外提供服务的，这时候系统处于不可用状态(失去了A属性)。</div></li></ol><li><div>最常见的例子是读写分离，某个节点负责写入数据，然后将数据同步到其它节点，其它节点提供读 取的服务，当两个节点出现通信问题时，你就面临着选择A（继续提供服务，但是数据不保证准 确），C（用户处于等待状态，一直等到数据同步完成）。</div></li></ol><div><br/></div><div>CAP总结</div><ol><li><div>分区是常态，不可避免，三者不可共存</div></li><li><div>可用性和一致性是一对冤家</div></li><ol><li><div>一致性高，可用性低</div></li><li><div>一致性低，可用性高</div></li></ol><li><div>因此，根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大 类：</div></li><ol><li><div>CA - 单点集群，满足一致性，可用性的系统，通常在可扩展性上不太强大。</div></li><li><div>CP - 满足一致性，分区容忍性的系统，通常性能不是特别高。</div></li><li><div>AP - 满足可用性，分区容忍性的系统，通常可能对一致性要求低一些。</div></li></ol></ol><div><br/></div><div>Redis相关操作</div><div><br/></div><div>清空当前库: flushdb</div><div><br/></div><div>清空所有（16个）库(<font style="color: rgb(255, 0, 0);">慎用</font>)！: flushall</div><div><br/></div><div>模糊查询keys命令： </div><ol><li><div>查询所有的键： keys *</div></li><li><div>模糊查询k开头，后面随便多少个字符： keys k*</div></li><li><div>模糊查询e为最后一位，前面随便多少个字符： keys *e</div></li><li><div>双 * 模式，匹配任意多个字符：查询包含k的键： keys *k*</div></li><li><div>?：通配单个字符： 模糊查询k字头，并且匹配一个字符： keys k?</div></li><li><div>你只记得第一个字母是k，他的长度是3： keys k??</div></li><li><div>[]：通配括号内的某一个字符： 记得其他字母，就第二个字母可能是a或e： keys r[ae]dis</div></li><li><div>exists key：判断某个key是否存在</div></li><li><div>move key db：移动（剪切，粘贴）键到几号库</div></li><li><div>ttl key：查看键还有多久过期（-1永不过期，-2已过期）</div></li><li><div>time to live 还能活多久</div></li><li><div>expire key 秒：为键设置过期时间（生命倒计时）</div></li><li><div>type key：查看键的数据类型</div></li></ol><div><br/></div><div>使用Redis的五大数据类型：</div><ol><ol><li><div>String</div></li><ol><li><div>set ：保存数据</div></li><li><div>get:获取数据</div></li><li><div>del:删除数据</div></li><li><div>append:追加数据</div></li><li><div>strlen:返回值的长度(字符数量)</div></li><li><div>incr：自增</div></li><li><div>decr：自减</div></li><li><div>incrby key 3:自增3</div></li><li><div>decrby key 3: 自减3</div></li><li><div>incr/decr/incrby/decrby：加减操作，操作的必须是数字类型</div></li><li><div>getrange/setrange：类似between...and...</div></li><li><div>set with expir：添加数据的同时设置生命周期</div></li><li><div>set if not exist：添加数据的时候判断是否已经存在，防止已存在的数据被覆盖掉</div></li><li><div>mset/mget/msetnx</div></li><li><div>getset：先get后set</div></li></ol><li><div>列表List</div></li><ol><li><div>push和pop，类似机枪AK47：push，压子弹，pop，射击出子弹</div></li><ol><li><div>lpush/rpush/lrange</div></li><ol><li><div>l：left 自左向右→添加 （从上往下添加）</div></li><li><div>r：right 自右向左←添加（从下往上添加）</div></li></ol><li><div>lpop/rpop：移除第一个元素（上左下右）</div></li><li><div>lindex：根据下标查询元素（从左向右，自上而下）</div></li><li><div>llen：返回集合长度</div></li><li><div>lrem：删除n个value</div></li><li><div>ltrim：截取指定范围的值，别的全扔掉</div></li><li><div>rpoplpush：从一个集合搞一个元素到另一个集合中（右出一个，左进一个）</div></li><li><div>lset：改变某个下标的某个值</div></li><li><div>linsert：插入元素（指定某个元素之前/之后）</div></li></ol></ol><li><div>集合Set</div></li><ol><li><div>sadd/smembers/sismember：添加/查看/判断是否存在</div></li><li><div>scard：获得集合中的元素个数</div></li><li><div>srem：删除集合中的元素</div></li><li><div>srandmember：从集合中随机获取几个元素</div></li><li><div>spop：随机出栈（移除）</div></li><li><div>smove：移动元素：将key1某个值赋值给key2</div></li></ol><li><div>哈希Hash: KV模式不变，但V是一个键值对</div></li><ol><li><div>hset/hget/hmset/hmget/hgetall/hdel：添加/得到/多添加/多得到/得到全部/删除属性</div></li><li><div>hlen：返回元素的属性个数</div></li><li><div>hexists：判断元素是否存在某个属性</div></li><li><div>hkeys/hvals：获得属性的所有key/获得属性的所有value</div></li><li><div>hincrby/hincrbyfloat：自增（整数）/自增（小数）</div></li><li><div>hsetnx：添加的时候，先判断是否存在</div></li></ol><li><div>有序集合Zset</div></li><ol><li><div>zadd/zrange （withscores）：添加/查询</div></li><li><div>zrangebyscore：模糊查询</div></li><ol><li><div>( ： 不包含</div></li><li><div>limit：跳过几个截取几个</div></li></ol><li><div>zrem：删除元素</div></li><li><div>zcard/zcount/zrank/zscore：集合长度/范围内元素个数/得元素下标/通过值得分数</div></li><li><div>zrevrank：逆序找下标（从下向上）</div></li><li><div>zrevrange：逆序查询</div></li><li><div>zrevrangebyscore：逆序范围查找</div></li></ol></ol></ol><div><br/></div><div><br/></div><div>持久化</div><div>RDB: Redis DataBase</div><ol><li><div>在指定的时间间隔内，将内存中的数据集的快照写入磁盘；</div></li><li><div>默认保存在/usr/local/bin中，文件名dump.rdb;</div></li></ol><div><br/></div><div><br/></div><div>AOF: Append Only File</div><ol><li><div>以日志的形式记录每个写操作；</div></li><li><div>将redis执行过的写指令全部记录下来（读操作不记录）；</div></li><li><div>只许追加文件，不可以改写文件；</div></li><li><div>redis在启动之初会读取该文件从头到尾执行一遍，这样来重新构建数据；</div></li></ol><div><br/></div><div>AOF与RDB共存么？谁优先？</div><div>我们查看redis.conf文件，AOF和RDB两种备份策略可以同时开启，那系统会怎样选择</div><ol><li><div>编辑appendonly.aof，胡搞乱码，保存退出</div></li><li><div>2. 启动redis 失败，所以是AOF优先载入来恢复原始数据！因为AOF比RDB数据保存的完整性更高！</div></li><li><div>3. 修复AOF文件，杀光不符合redis语法规范的代码： reids-check-aof --fix appendonly.aof</div></li></ol><div><br/></div><div>与AOF相关的配置：</div><ol><li><div>appendonly：开启aof模式</div></li><li><div>appendfilename：aof的文件名字，最好别改！</div></li><li><div>appendfsync：追写策略</div></li><ol><li><div>always：每次数据变更，就会立即记录到磁盘，性能较差，但数据完整性好</div></li><li><div>everysec：默认设置，异步操作，每秒记录，如果一秒内宕机，会有数据丢失</div></li><li><div>no：不追写</div></li></ol><li><div>no-appendfsync-on-rewrite：重写时是否运用Appendfsync追写策略；用默认no即可，保证数据 安全性。</div></li><ol><li><div>AOF采用文件追加的方式，文件会越来越大，为了解决这个问题，增加了重写机制，redis会 自动记录上一次AOF文件的大小，当AOF文件大小达到预先设定的大小时，redis就会启动 AOF文件进行内容压缩，只保留可以恢复数据的最小指令集合</div></li></ol><li><div>auto-aof-rewrite-percentage：如果AOF文件大小已经超过原来的100%，也就是一倍，才重写压 缩</div></li><li><div>auto-aof-rewrite-min-size：如果AOF文件已经超过了64mb，才重写压缩</div></li></ol><div><br/></div><div><br/></div><div><br/></div><div>总结：</div><ol><li><div>RDB：只用作后备用途，建议15分钟备份一次就好</div></li><li><div>AOF：</div></li><ol><li><div>在最恶劣的情况下，也只丢失不超过2秒的数据，数据完整性比较高，但代价太大，会带来持 续的IO</div></li><li><div>对硬盘的大小要求也高，默认64mb太小了，企业级最少都是5G以上；</div></li><li><div>后面要学习的master/slave才是新浪微博的选择！！</div></li></ol></ol><div><br/></div><div><br/></div><div>事务：</div><ol><li><div>可以一次执行多个命令，是一个命令组，一个事务中，所有命令都会序列化（排队），不会被插 队；</div></li><li><div>一个队列中，一次性，顺序性，排他性的执行一系列命令</div></li><li><div>三特性</div></li><ol><li><div>隔离性：所有命令都会按照顺序执行，事务在执行的过程中，不会被其他客户端送来的命令 打断</div></li><li><div>没有隔离级别：队列中的命令没有提交之前都不会被实际的执行，不存在“事务中查询要看到 事务里的更新，事务外查询不能看到”这个头疼的问题</div></li><li><div>不保证原子性：冤有头债有主，如果一个命令失败，但是别的命令可能会执行成功，没有回 滚</div></li></ol><li><div>三步走</div></li><ol><li><div>开启multi</div></li><li><div>入队queued</div></li><li><div>执行exec</div></li></ol><li><div>与关系型数据库事务相比，</div></li><ol><li><div>multi：可以理解成关系型事务中的 begin</div></li><li><div>exec ：可以理解成关系型事务中的 commit</div></li><li><div>discard ：可以理解成关系型事务中的 rollback</div></li></ol><li><div>一起生： 开启事务，加入队列，一起执行，并成功</div></li><li><div>一起死： 放弃之前的操作，恢复到原来的值</div></li><li><div>一句报错，全部取消，恢复到原来的值</div></li></ol><div><br/></div><div>Redis的发布订阅： subscribe cctv1 cctv5 cctv6 # 1.订阅三个频道</div><div><br/></div><div>主从复制</div><ol><li><div>就是 redis集群的策略</div></li><li><div>配从（库）不配主（库）：小弟可以选择谁是大哥，但大哥没有权利去选择小弟</div></li><li><div>读写分离：主机写，从机读</div></li></ol><div><br/></div><div>血脉相传</div><ol><li><div>一个主机理论上可以多个从机，但是这样的话，这个主机会很累</div></li><li><div>我们可以使用java面向对象继承中的传递性来解决这个问题，减轻主机的负担</div></li><li><div>形成祖孙三代： slaveof 192.168.204.141 6379： 142跟随141</div></li></ol><div><br/></div><div>1个主机，2个从机，当1个主机挂掉了，只能从2个从机中再次选1个主机</div><div>当1再次回归： 2和3已经形成新的集群，和1没有任何的关系了。所以1成为了光杆司令</div><div><br/></div><div><br/></div><div>哨兵模式</div><ol><li><div>哨兵模式</div></li><li><div>有个哨兵一直在巡逻，突然发现！！！！！老大挂了，小弟们会自动投票，从众小弟中选出新的老 大</div></li><li><div>Sentinel是Redis的高可用性解决方案：</div></li><ol><li><div>由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器，以及所有从服务 器，并在被监视的主服务器进入下线状态时，自动将下线主服务器属下的某个从服务器升级 为新的主服务器，然后由新的主服务器代替已下线的主服务器继续处理命令请求</div></li></ol><li><div>启动服务的顺序：主Redis --&gt; 从Redis --&gt; Sentinel1/2/3</div></li><li><div>将1号老大挂掉，后台自动发起激烈的投票，选出新的老大</div></li><li><div>查看最后权利的分配</div></li><ol><li><div>3成为了新的老大，2还是小弟</div></li><li><div>6. 如果之前的老大再次归来呢？</div></li><ol><li><div>1号再次归来，自己成为了master，和3平起平坐</div></li><li><div>过了几秒之后，被哨兵检测到了1号机的归来，1号你别自己玩了，进入集体吧，但是新 的老大已经产生了，你只能作为小弟再次进入集体！</div></li></ol></ol></ol><div>哨兵模式： 缺点</div><ol><li><div>由于所有的写操作都是在master上完成的；</div></li><li><div><span style="color: unset; font-family: unset; font-size: unset;">然后再同步到slave上，所以两台机器之间通信会有延迟；</span></div></li><li><div>当系统很繁忙的时候，延迟问题会加重；</div></li><li><div>当系统很繁忙的时候，延迟问题会加重；</div></li></ol><div><br/></div><div><br/></div><div>java和redis打交道的API客户端</div><div>依赖：</div><div><img src="Redis&FastDFS&RabbitMQ_files/Image.png" type="image/png" data-filename="Image.png"/></div><div><br/></div><div><br/></div><div>实现分布式锁的思路</div><ol><li><div>1. 因为redis是单线程的，所以命令也就具备原子性，使用setnx命令实现锁，保存k-v</div></li><ol><li><div>如果k不存在，保存（当前线程加锁），执行完成后，删除k表示释放锁</div></li><li><div>如果k已存在，阻塞线程执行，表示有锁</div></li></ol><li><div>如果加锁成功，在执行业务代码的过程中出现异常，导致没有删除k（释放锁失败），那么就会造 成死锁（后面的所有线程都无法执行）！</div></li><ol><li><div>设置过期时间，例如10秒后，redis自动删除</div></li></ol><li><div>3. 高并发下，由于时间段等因素导致服务器压力过大或过小，每个线程执行的时间不同</div></li><ol><li><div>第一个线程，执行需要13秒，执行到第10秒时，redis自动过期了k（释放锁）</div></li><li><div>第二个线程，执行需要7秒，加锁，执行第3秒（锁 被释放了，为什么，是被第一个线程的 finally主动deleteKey释放掉了）</div></li><li><div>。。。连锁反应，当前线程刚加的锁，就被其他线程释放掉了，周而复始，导致锁会永久失 效</div></li><li><div>给每个线程加上唯一的标识UUID随机生成，释放的时候判断是否是当前的标识即可</div></li><li><div>. 问题又来了，过期时间如果设定？</div></li><ol><li><div>如果10秒太短不够用怎么办？</div></li><li><div>设置60秒，太长又浪费时间</div></li><li><div>可以开启一个定时器线程，当过期时间小于总过期时间的1/3时，增长总过期时间（吃仙丹续 命！）</div></li></ol></ol></ol><div><br/></div><div>Redisson：</div><ol><li><div>Redis 是最流行的 NoSQL 数据库解决方案之一</div></li><li><div>虽然两者看起来很自然地在一起“工作”，但是要知道，Redis 其实并没有对 Java 提供原生支持。</div></li><li><div>相反，作为 Java 开发人员，我们若想在程序中集成 Redis，必须使用 Redis 的第三方库。</div></li><li><div>而 Redisson 就是用于在 Java 程序中操作 Redis 的库，它使得我们可以在程序中轻松地使用 Redis。</div></li><li><div>Redisson 在 java.util 中常用接口的基础上，为我们提供了一系列具有分布式特性的工具类。</div></li><li><div>实现分布式锁的方案其实有很多，我们之前用过的zookeeper的特点就是高可靠性，现在我们用的 redis特点就是高性能。</div></li><li><div>目前分布式锁，应用最多的仍然是“Redis”</div></li></ol><div><br/></div><div><font style="font-size: 16pt;"><br/></font></div><div><font style="font-size: 16pt;">分布式文件系统—FastDFS</font></div><div><font style="font-size: 10pt;">场景概述：</font></div><ol><li><div>天猫，淘宝等购物网站，大量的图片和视频，文件太多，如何存储？</div></li><li><div>用户访问量大又如何保证下载速度？分布式文件系统就是解决这些问题的！</div></li></ol><div><br/></div><div>分布式文件系统</div><ol><li><div>一台电脑存储量有限，并且并发吞吐量也有限，如何提高性能？</div></li><li><div>一吨货物，我要运送到吐鲁番：</div></li><ol><li><div>1个人运，不敢想象</div></li><li><div>50个人运，太难了；</div></li><li><div>500个人运，每个人都很轻松；</div></li></ol><li><div>这就是分布式吗？</div></li><ol><li><div>答：这里面有集群的概念，也有分布式的概念，二者不要混淆，面试常问的经典题目</div></li><ol><li><div>分布式：不同的业务模块部署在不同的服务器上或者同一个业务模块分拆多个子业务，部署不同的 服务器上。解决高并发的问题；</div></li><li><div>集群：同一个业务部署在多台服务器上，提高系统的高可用</div></li></ol></ol></ol><div><br/></div><div>主流的分布式文件系统</div><div>HDFS</div><ol><li><div>(Hadoop Distributed File System)Hadoop 分布式文件系统；</div></li><li><div>高容错的系统，适合部署到廉价的机器上；</div></li><li><div>能提供高吞吐量的数据访问，非常适合大规模数据应用；</div></li><li><div>HDFS采用主从结构，一个HDFS是由一个name节点和N个data节点组成；</div></li><li><div>name节点储存元数据，一个文件分割成N份存储在不同的data节点上。</div></li></ol><div><br/></div><div>GFS</div><ol><li><div>Google File System</div></li><li><div>可扩展的分布式文件系统，用于大型的，分布式的，对大量数据进行访问的应用；</div></li><li><div>运行于廉价的普通硬件上，可以提供容错功能；</div></li><li><div>它可以给大量的用户提供总体性能较高的服务；</div></li><li><div>GFS采用主从结构，一个GFS集群由一个master和大量的chunkserver（分块服务器）组成；</div></li><li><div>一个文件被分割若干块，分散储存到多个分块server中</div></li></ol><div><br/></div><div>FastDFS</div><ol><li><div>由淘宝资深架构师余庆编写并开源；</div></li><li><div>专为互联网量身定制，充分考虑了冗余备份、负载均衡、线性扩容等机制，并注重高可用、高性能 等指标，使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务；</div></li><li><div>HDFS，GFS等都是通用的文件系统，他们的优点是开发体验好，但是系统的复杂度较高，性能也 一般；</div></li><li><div>相比之下，专用的分布式文件系统体验差，但是复杂度低，性能也高，尤其fastDFS特别适合图 片，小视频等小文件，因为fastDFS对文件是不分割的，所以没有文件合并的开销；</div></li><li><div>网络通信用socket，速度快。</div></li></ol><div><br/></div><div>工作原理</div><ol><li><div>fastDFS包含Tracker Server和Storage Server；</div></li><li><div>客户端请求Tracker Server进行文件的上传与下载；</div></li><li><div>Tracker Server调度Storage Server最终完成上传与下载。</div></li><li><div>Tracker （译：追踪者）</div></li><ol><li><div>作用是负载均衡和调度，它管理着存储服务（Storage Server），可以理解为：“大管家，追 踪者，调度员”</div></li><li><div>Tracker Server可以集群，实现高可用，策略为“轮询”。</div></li></ol><li><div>Storage （译：仓库; 贮存器）</div></li><ol><li><div>作用是文件存储，客户端上传的文件最终存储到storage服务器上；</div></li><li><div>storage集群采用分组的方式，同组内的每台服务器是平等关系，数据同步，目的是实现数据 备份，从而高可用，而不同组的服务器之间是不通信的；</div></li><li><div>同组内的每台服务器的存储量不一致的情况下，会选取容量最小的那个，所以同组内的服务 器之间软硬件最好保持一致。</div></li><li><div>Storage Server会连接集群中的所有Tracker Server，定时向他们汇报自己的状态，例如：剩 余空间，文件同步情况，文件上传下载次数等信息。</div></li></ol></ol><div><br/></div><div>上传/下载 原理</div><ol><li><div>客户端上传文件后，storage会将文件id返回给客户端</div></li><li><div>group1/M00/02/11/aJxAeF21O5wAAAAAAAAGaEIOA12345.sh</div></li><ol><li><div>组名：文件上传后，在storage组的名称，文件上传成功后，由storage返回，需要客户端自 行保存</div></li><li><div>虚拟磁盘路径：</div></li><ol><li><div>storage配置的虚拟路径，在磁盘选项storage_path对应。</div></li><li><div>storage_path0对应M00，</div></li><li><div>storage_path1对应M01，</div></li></ol><li><div>数据两级目录： storage在虚拟磁盘下自行创建的目录。</div></li><li><div>文件名： 与上传时不同，是用storage根据特定信息生成的，里面包含：storage服务器的ip，创建时 间戳，大小，后缀名等信息</div></li></ol></ol><div><br/></div><div><br/></div><div><img src="Redis&FastDFS&RabbitMQ_files/Image [1].png" type="image/png" data-filename="Image.png"/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><font style="font-size: 16pt;">RabbitMQ</font></div><div><br/></div><div><br/></div><div>什么是RabbitMQ：</div><div>MQ（Message Queue）消息队列</div><ol><li><div>MQ（Message Queue）消息队列</div></li><li><div>主要解决，异步处理，应用解耦，流量削峰等问题</div></li><li><div>从而实现高性能，高可用，可伸缩和最终一致性的架构</div></li><li><div>使用较多的消息队列产品：RabbitMQ，RocketMQ，ActiveMQ，ZeroMQ，Kafka等</div></li></ol><div><br/></div><div>异步处理</div><ol><li><div>用户注册后，需要发送验证邮箱和手机验证码；</div></li><li><div>将注册信息写入数据库，发送验证邮件，发送手机，三个步骤全部完成后，返回给客户端</div></li></ol><div><br/></div><div><br/></div><div>应用解耦</div><ol><li><div>场景：订单系统需要通知库存系统</div></li><li><div>如果库存系统异常，则订单调用库存失败，导致下单失败： 原因：订单系统和库存系统耦合度太高</div></li><li><div>订单系统：用户下单后，订单系统完成持久化处理，将消息写入消息队列，返回用户，下单成功；</div></li><li><div>库存系统：订阅下单的消息，获取下单信息，库存系统根据下单信息，再进行库存操作；</div></li><li><div>假如：下单的时候，库存系统不能正常运行，也不会影响下单，因为下单后，订单系统写入消息队 列就不再关心其他的后续操作了，实现了订单系统和库存系统的应用解耦；</div></li><li><div>所以说，消息队列是典型的：生产者消费者模型</div></li><li><div>生产者不断的向消息队列中生产消息，消费者不断的从队列中获取消息</div></li><li><div>因为消息的生产和消费都是异步的，而且只关心消息的发送和接收，没有业务逻辑的入侵，这样就 实现了生产者和消费者的解耦</div></li></ol><div><br/></div><div>流量削峰</div><ol><li><div>抢购，秒杀等业务，针对高并发的场景</div></li><li><div>因为流量过大，暴增会导致应用挂掉，为解决这个问题，在前端加入消息队列</div></li><li><div>用户的请求，服务器接收后，首先写入消息队列，如果超过队列的长度，就抛弃，甩一个秒杀结束 的页面！</div></li><li><div>说白了，秒杀成功的就是进入队列的用户；</div></li></ol><div><br/></div><div><br/></div><div>AMQP高级消息队列协议</div><ol><li><div>即Advanced Message Queuing Protocol，一个提供统一消息服务的应用层标准高级消息队列协 议</div></li><li><div>协议：数据在传输的过程中必须要遵守的规则</div></li><li><div>基于此协议的客户端可以与消息中间件传递消息</div></li><li><div>并不受产品、开发语言等条件的限制</div></li></ol><div><br/></div><div>JMS</div><ol><li><div>Java Message Server，Java消息服务应用程序接口， 一种规范，和JDBC担任的角色类似</div></li><li><div>是一个Java平台中关于面向消息中间件的API，用于在两个应用程序之间，或分布式系统中发送消 息，进行异步通信</div></li></ol><div><br/></div><div><br/></div><div>二者的联系</div><ol><li><div>JMS是定义了统一接口，统一消息操作；AMQP通过协议统一数据交互格式</div></li><li><div>JMS必须是java语言；AMQP只是协议，与语言无关</div></li></ol><div><br/></div><div>Erlang语言</div><ol><li><div>Erlang（['ə:læŋ]）是一种通用的面向并发的编程语言，它由瑞典电信设备制造商爱立信所辖的CSLab开发，目的是创造一种可以应对大规模并发活动的编程语言和运行环境</div></li><li><div>最初是由爱立信专门为通信应用设计的，比如控制交换机或者变换协议等，因此非常适合构建分布 式，实时软并行计算系统</div></li><li><div>Erlang运行时环境是一个虚拟机，有点像Java的虚拟机，这样代码一经编译，同样可以随处运行</div></li></ol><div><br/></div><div>为什么选择RabbitMQ</div><ol><li><div>先看命名：兔子行动非常迅速而且繁殖起来也非常疯狂，所以就把Rabbit用作这个分布式软件的 命名（就是这么简单）</div></li><li><div>Erlang开发，AMQP的最佳搭档，安装部署简单，上手门槛低</div></li><li><div>企业级消息队列，经过大量实践考验的高可靠，大量成功的应用案例，例如阿里、网易等一线大厂 都有使用</div></li><li><div>有强大的WEB管理页面</div></li><li><div>强大的社区支持，为技术进步提供动力</div></li><li><div>支持消息持久化、支持消息确认机制、灵活的任务分发机制等，支持功能非常丰富</div></li><li><div>集群扩展很容易，并且可以通过增加节点实现成倍的性能提升</div></li><li><div>总结：如果你希望使用一个可靠性高、功能强大、易于管理的消息队列系统那么就选择 RabbitMQ，如果你想用一个性能高，但偶尔丢点数据不是很在乎可以使用kafka或者zeroMQ</div></li><li><div>kafka和zeroMQ的性能爆表，绝对可以压RabbitMQ一头！</div></li></ol><div><br/></div><div>RabbitMQ各组件功能</div><ol><li><div>Broker：消息队列服务器实体</div></li><li><div>Virtual Host：虚拟主机</div></li><ol><li><div>标识一批交换机、消息队列和相关对象，形成的整体</div></li><li><div>虚拟主机是共享相同的身份认证和加密环境的独立服务器域</div></li><li><div>每个vhost本质上就是一个mini版的RabbitMQ服务器，拥有自己的队列、交换器、绑定和权 限机制</div></li><li><div>vhost是AMQP概念的基础，RabbitMQ默认的vhost是 /，必须在链接时指定</div></li></ol><li><div>Exchange：交换器（路由）： 用来接收生产者发送的消息并将这些消息路由给服务器中的队列</div></li><li><div>Queue：消息队列</div></li><ol><li><div>用来保存消息直到发送给消费者。</div></li><li><div>它是消息的容器，也是消息的终点。</div></li><li><div>一个消息可投入一个或多个队列。</div></li><li><div>消息一直在队列里面，等待消费者连接到这个队列将其取走。</div></li></ol><li><div>Banding：绑定，用于消息队列和交换机之间的关联。</div></li><li><div>Channel：通道（信道）</div></li><ol><li><div>多路复用连接中的一条独立的双向数据流通道。</div></li><li><div>信道是建立在真实的TCP连接内的 虚拟链接</div></li><li><div>AMQP命令都是通过信道发出去的，不管是发布消息、订阅队列还是接收消息，都是通过信 道完成的</div></li><li><div>因为对于操作系统来说，建立和销毁TCP连接都是非常昂贵的开销，所以引入了信道的概 念，用来复用TCP连接。</div></li></ol><li><div>Connection：网络连接，比如一个TCP连接。</div></li><li><div>Publisher：消息的生产者，也是一个向交换器发布消息的客户端应用程序。</div></li><li><div>Consumer：消息的消费者，表示一个从消息队列中取得消息的客户端应用程序。</div></li><li><div>Message：消息</div></li><ol><li><div>消息是不具名的，它是由消息头和消息体组成。</div></li><li><div>消息体是不透明的，而消息头则是由一系列的可选属性组成，这些属性包括routing-key(路由 键)、priority(优先级)、delivery-mode(消息可能需要持久性存储[消息的路由模式])等。<br/></div></li></ol></ol><div><br/></div><div>注意：安装rabbitmq前必须先安装erlang语言环境</div><div><br/></div><div><br/></div><div>RabbitMQ模式</div><ol><li><div>5种消息模型，大体分为两类：</div></li><ol><li><div>1和2属于点对点</div></li><li><div>3、4、5属于发布订阅模式（一对多）</div></li></ol><li><div>点对点模式：P2P（point to point）模式包含三个角色：</div></li><ol><li><div>消息队列（queue），发送者（sender），接收者（receiver）</div></li><li><div>每个消息发送到一个特定的队列中，接收者从中获得消息</div></li><li><div>队列中保留这些消息，直到他们被消费或超时</div></li><li><div>特点：</div></li><ol><li><div>每个消息只有一个消费者，一旦消费，消息就不在队列中了</div></li><li><div>发送者和接收者之间没有依赖性，发送者发送完成，不管接收者是否运行，都不会影响 消息发送到队列中（我给你发微信，不管你看不看手机，反正我发完了）</div></li><li><div>接收者成功接收消息之后需向对象应答成功（确认）</div></li></ol><li><div>如果希望发送的每个消息都会被成功处理，那需要P2P</div></li></ol><li><div>发布订阅模式：publish（Pub）/subscribe（Sub）</div></li><ol><li><div>pub/sub模式包含三个角色：交换机（exchange），发布者（publisher），订阅者 （subcriber）</div></li><li><div>多个发布者将消息发送交换机，系统将这些消息传递给多个订阅者</div></li><li><div>特点：</div></li><ol><li><div>每个消息可以有多个订阅者</div></li><li><div>发布者和订阅者之间在时间上有依赖，对于某个交换机的订阅者，必须创建一个订阅 后，才能消费发布者的消息</div></li><li><div>为了消费消息，订阅者必须保持运行状态；类似于，看电视直播。</div></li></ol><li><div>如果希望发送的消息被多个消费者处理，可采用本模式</div></li></ol></ol><div><br/></div><div><br/></div><div>消息确认机制ACK</div><ol><li><div>消息一旦被消费，消息就会立刻从队列中移除</div></li><li><div>RabbitMQ如何得知消息被消费者接收？</div></li><ol><li><div>如果消费者接收消息后，还没执行操作就抛异常宕机导致消费失败，但是RabbitMQ无从得 知，这样消息就丢失了</div></li><li><div>因此，RabbitMQ有一个ACK机制，当消费者获取消息后，会向RabbitMQ发送回执ACK，告 知消息已经被接收</div></li><li><div>ACK：(Acknowledge character）即是确认字符，在数据通信中，接收站发给发送站的一种 传输类控制字符。表示发来的数据已确认接收无误我们在使用http请求时，http的状态码200 就是告诉我们服务器执行成功</div></li><li><div>整个过程就想快递员将包裹送到你手里，并且需要你的签字，并拍照回执</div></li><li><div>不过这种回执ACK分为两种情况：</div></li><ol><li><div>自动ACK：消息接收后，消费者立刻自动发送ACK（快递放在快递柜）</div></li><li><div>手动ACK：消息接收后，不会发送ACK，需要手动调用（快递必须本人签收）</div></li></ol><li><div>两种情况如何选择，需要看消息的重要性：</div></li><ol><li><div>如果消息不太重要，丢失也没有影响，自动ACK会比较方便</div></li><li><div>如果消息非常重要，最好消费完成手动ACK，如果自动ACK消费后，RabbitMQ就会把 消息从队列中删除，如果此时消费者抛异常宕机，那么消息就永久丢失了</div></li></ol><li><div>修改手动消息确认： channel.basicConsume(&quot;queue1&quot;, false, consumer);   false：手动消息确认</div></li></ol></ol><div><br/></div><div><br/></div><div><br/></div><div>工作队列模式</div><ol><li><div>简单模式，一个消费者来处理消息，如果生产者生产消息过快过多，而消费者的能 力有限，就会产生消息在队列中堆积（生活中的滞销）</div></li><li><div>一个烧烤师傅，一次烤50支羊肉串，就一个人吃的话，烤好的肉串会越来越多，怎么处理？</div></li><li><div>多招揽客人进行消费即可。当我们运行许多消费者程序时，消息队列中的任务会被众多消费者共 享，但其中某一个消息只会被一个消费者获取（100支肉串20个人吃，但是其中的某支肉串只能被 一个人吃）</div></li></ol><div><font color="#FF0000"><br/></font></div><div><font color="#FF0000">面试题：避免消息堆积？</font></div><ol><li><div><font color="#FF0000">workqueue，多个消费者监听同一个队列</font></div></li><li><div><font color="#FF0000">接收到消息后，通过线程池，异步消费</font></div></li></ol><div><br/></div><div>发布订阅模式</div><ol><li><div>P生产者发送信息给X路由，X将信息转发给绑定X的队列</div></li><li><div>X队列将信息通过信道发送给消费者，从而进行消费</div></li><li><div>整个过程，必须先创建路由</div></li><ol><li><div>路由在生产者程序中创建</div></li><li><div>因为路由没有存储消息的能力，当生产者将信息发送给路由后，消费者还没有运行，所以没 有队列，路由并不知道将信息发送给谁</div></li><li><div>运行程序的顺序：</div></li><ol><li><div>MessageSender</div></li><li><div>MessageReceiver1和MessageReceiver2</div></li><li><div>MessageSender</div></li></ol></ol></ol><div><br/></div><div><br/></div><div><br/></div><div>路由模式</div><ol><li><div>路由会根据类型进行定向分发消息给不同的队列，如图所示</div></li><li><div>可以理解为是快递公司的分拣中心，整个小区，东面的楼小张送货，西面的楼小王送货</div></li></ol><div><br/></div><div><br/></div><div>通配符模式</div><ol><li><div>和路由模式90%是一样的。</div></li><li><div>唯独的区别就是路由键支持模糊匹配</div></li><li><div>匹配符号</div></li><ol><li><div>*：只能匹配一个词（正好一个词，多一个不行，少一个也不行）</div></li><li><div>#：匹配0个或更多个词</div></li></ol></ol><div><br/></div><div><br/></div><div>持久化：</div><ol><li><div>消息的可靠性是RabbitMQ的一大特色，那么RabbitMQ是如何避免消息丢失？</div></li><ol><li><div>消费者的ACK确认机制，可以防止消费者丢失消息</div></li><li><div>万一在消费者消费之前，RabbitMQ服务器宕机了，那消息也会丢失</div></li></ol><li><div>想要将消息持久化，那么 路由和队列都要持久化 才可以</div></li></ol><div><br/></div><div><br/></div><div><br/></div><div>消息成功确认机制</div><ol><li><div>在实际场景下，有的生产者发送的消息是必须保证成功发送到消息队列中，那么如何保证成功投递呢？</div></li><ol><li><div>事务机制</div></li><li><div>发布确认机制</div></li></ol><li><div>事务机制</div></li><ol><li><div>AMQP协议提供的一种保证消息成功投递的方式，通过信道开启 transactional 模式</div></li><li><div>并利用信道 的三个方法来实现以事务方式 发送消息，若发送失败，通过异常处理回滚事务，确保 消息成功投递</div></li><ol><li><div>channel.txSelect()： 开启事务</div></li><li><div>channel.txCommit() ：提交事务</div></li><li><div>channel.txRollback() ：回滚事务</div></li></ol><li><div>Spring已经对上面三个方法进行了封装，所以我们只能使用原始的代码演示</div></li></ol></ol><div><br/></div><div><br/></div><div>Confirm发布确认机制</div><ol><li><div>RabbitMQ为了保证消息的成功投递，采用通过AMQP协议层面为我们提供事务机制的方案，但是 采用事务会大大降低消息的吞吐量</div></li><li><div>本机SSD硬盘测试结果10w条消息未开启事务，大约8s发送完毕；而开启了事务后，需要将 近310s，差了30多倍。</div></li><li><div>Confirm模式是比开启事务更加高效的方法</div></li><li><div>事务效率为什么会这么低呢？试想一下：10条消息，前9条成功，如果第10条失败，那么9条消息 要全部撤销回滚。太太太浪费</div></li><li><div>而confirm模式则采用补发第10条的措施来完成10条消息的送达</div></li></ol><div><br/></div><div><br/></div><div>消费端限流：</div><ol><li><div>在沙漠中行走，3天不喝水，突然喝水，如果使劲喝，容易猝死，要一口一口慢慢喝</div></li><li><div>我们 Rabbitmq 服务器积压了成千上万条未处理的消息，然后随便打开一个消费者客户端，就会 出现这样的情况: 巨量的消息瞬间全部喷涌推送过来，但是单个客户端无法同时处理这么多数据， 就会被压垮崩溃</div></li><li><div>所以，当数据量特别大的时候，我们对生产端限流肯定是不科学的，因为有时候并发量就是特别 大，有时候并发量又特别少，这是用户的行为，我们是无法约束的</div></li><li><div>所以我们应该对消费端限流，用于保持消费端的稳定</div></li><li><div>例如：汽车企业不停的生产汽车，4S店有好多库存车卖不出去，但是也不会降价处理，就是要保 证市值的稳定，如果生产多少台，就卖多少台，不管价格的话，市场就乱了，所以我们要用不变的 价格来稳住消费者购车，才能平稳发展</div></li><li><div>RabbitMQ 提供了一种 Qos （Quality of Service，服务质量）服务质量保证功能： 即在非自动确认消息的前提下，如果一定数目的消息未被确认前，不再进行消费新的消息</div></li></ol><div><br/></div><div><br/></div><div>过期时间TTL： Time To Live：生存时间、还能活多久，单位毫秒</div><ol><li><div>在这个周期内，消息可以被消费者正常消费，超过这个时间，则自动删除（其实是被称为dead message并投入到死信队列，无法消费该消息）</div></li><li><div>RabbitMQ可以对消息和队列设置TTL</div></li><li><div>通过队列设置，队列中所有消息都有相同的过期时间</div></li><li><div>对消息单独设置，每条消息的TTL可以不同（更颗粒化）</div></li></ol><div><br/></div><div><br/></div><div>死信队列</div><ol><li><div>DLX（Dead Letter Exchanges）死信交换机/死信邮箱，当消息在队列中由于某些原因没有被及时 消费而变成死信（dead message）后，这些消息就会被分发到DLX交换机中，而绑定DLX交换机 的队列，称之为：“死信队列”</div></li><li><div>消息没有被及时消费的原因：</div></li><ol><li><div>消息被拒绝（basic.reject/ basic.nack）并且不再重新投递 requeue=false</div></li><li><div>消息超时未消费</div></li><li><div>达到最大队列长度</div></li></ol></ol><div><br/></div><div>延迟队列</div><ol><li><div>延迟队列：TTL + 死信队列的合体</div></li><li><div>死信队列只是一种特殊的队列，里面的消息仍然可以消费</div></li><li><div>在电商开发部分中，都会涉及到延时关闭订单，此时延迟队列正好可以解决这个问题</div></li></ol><div><br/></div><div><br/></div><div>RabbitMQ集群</div><div><span style="color: unset; font-family: unset; font-size: unset;">rabbitmq有3种模式，但集群模式是2种。详细如下：</span></div><ol><li><div>单一模式：即单机情况不做集群，就单独运行一个rabbitmq而已。</div></li><li><div>普通模式：默认模式，以两个节点（A、B）为例来进行说明</div></li><ol><li><div>当消息进入A节点的Queue后，consumer从B节点消费时，RabbitMQ会在A和B之间创建临 时通道进行消息传输，把A中的消息实体取出并经过通过交给B发送给consumer</div></li><li><div>当A故障后，B就无法取到A节点中未消费的消息实体</div></li><ol><li><div>如果做了消息持久化，那么得等A节点恢复，然后才可被消费</div></li><li><div>如果没有持久化的话，就会产生消息丢失的现象</div></li></ol><li><div>镜像模式：非常经典的 mirror 镜像模式，保证 100% 数据不丢失。</div></li><ol><li><div>高可靠性解决方案，主要就是实现数据的同步，一般来讲是 2 - 3 个节点实现数据同步</div></li><li><div>对于 100% 数据可靠性解决方案，一般是采用 3 个节点。</div></li><li><div>在实际工作中也是用得最多的，并且实现非常的简单，一般互联网大厂都会构建这种镜像集 群模式</div></li></ol><li><div>还有主备模式，远程模式，多活模式等</div></li></ol></ol><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div><div><br/></div></div><div><br/></div></span>
</div></body></html> 